// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/base/config/args.h"

#include <utility>

#include <toml.hpp>

#include "coin2/base/log.h"
#include "coin2/base/string_util.h"
#include "coin2/base/time.h"

namespace coin2::base::config {

void AddDriverOptions(cxxopts::Options* opt) {
  opt->add_options()(
      "driver",
      "DriverConfig json file",
      cxxopts::value<std::vector<std::experimental::filesystem::path>>());
}

void AddIntervalOptions(cxxopts::Options* opt) {
  opt->add_options()("start", "start timestamp", cxxopts::value<std::string>())(
      "end",
      "end timestamp",
      cxxopts::value<std::string>())(
      "duration",
      "duration timestamp",
      cxxopts::value<std::string>());
}

void AddProductOptions(cxxopts::Options* opt) {
  opt->add_options()("groups", "symbol groups", cxxopts::value<std::string>())(
      "natives",
      "native symbols",
      cxxopts::value<std::string>())("norms", "norm symbols", cxxopts::value<std::string>());
}

void AddLogArchiveOptions(cxxopts::Options* opt) {
  opt->add_options()(
      "log_root",
      "log root",
      cxxopts::value<std::experimental::filesystem::path>()->default_value(
          "/remote/iosg/strat-1/buckets/log.raw.coin/live"));
  opt->add_options()(
      "log_type",
      "log type",
      cxxopts::value<std::string>()->default_value("strat_proto_log"));
  opt->add_options()(
      "log_machine",
      "log machine",
      cxxopts::value<std::string>());
  opt->add_options()(
      "log_strategy_name",
      "log strategy name",
      cxxopts::value<std::string>());
}

void AddFeedArchiveOptions(cxxopts::Options* opt) {
  opt->add_options()("feed_machine", "feed_machine", cxxopts::value<std::string>());
}

void ParseIntervalOptions(const cxxopts::ParseResult& res, coin2::base::IntervalConfig* config) {
  if (res.count("start") != 0U) {
    config->set_start(res["start"].as<std::string>());
  }
  if (res.count("end") != 0U) {
    config->set_end(res["end"].as<std::string>());
  }
  if (res.count("duration") != 0U) {
    config->set_duration(res["duration"].as<std::string>());
  }
}

std::pair<int64_t, int64_t> ParseIntervalConfig(const toml::value& v) {
  const auto& table = v.as_table();

  if (table.find("interval") != table.end()) {
    return IntervalFromIso8601(table.at("interval").as_string());
  }

  int64_t start = 0;
  int64_t end = 0;
  int64_t duration = 0;
  if (table.find("start") != table.end()) {
    if (table.at("start").is_integer()) {
      start = table.at("start").as_integer();
    } else {
      start = TimestampFromString(table.at("start").as_string());
    }
    CHECK_GT_THROW(start, 0) << "time should be positive";
  }
  if (table.find("end") != table.end()) {
    if (table.at("end").is_integer()) {
      end = table.at("end").as_integer();
    } else {
      end = TimestampFromString(table.at("end").as_string());
    }
    CHECK_GT_THROW(end, 0) << "time should be positive";
  }
  if (table.find("duration") != table.end()) {
    if (table.at("duration").is_integer()) {
      duration = table.at("duration").as_integer();
    } else {
      duration = DurationFromIso8601(table.at("duration").as_string());
    }

    CHECK_GT_THROW(duration, 0) << "duration should be positive";

    if (start != 0) {
      end = start + duration;
    } else {
      start = end - duration;
    }
  }

  CHECK_EQ_THROW((start != 0) + (end != 0) + (duration != 0), 2) << "wrong interval";
  return std::make_pair(start, end);
}

std::vector<nlohmann::json> ParseJsonOptions(const cxxopts::ParseResult& res) {
  std::vector<nlohmann::json> out;
  if (res.count("json") != 0U) {
    auto paths = res["json"].as<std::vector<std::experimental::filesystem::path>>();
    for (auto& path : paths) {
      std::ifstream file(path);
      out.push_back(nlohmann::json::parse(file));
    }
  }
  return out;
}

bool ParseDriverOptions(const cxxopts::ParseResult& res, coin2::app::DriverConfig* config) {
  if (res.count("driver") != 0U) {
    auto paths = res["driver"].as<std::vector<std::experimental::filesystem::path>>();
    for (auto& path : paths) {
      config->MergeFrom(coin2::base::config::DriverConfigFromJson(path));
    }
    return true;
  }
  return false;
}

// MEA1=symbol1,symbol2;MEA2=symbol1,symbol2
std::map<std::string, std::vector<std::string>> ParseProductList(const std::string& s) {
  std::vector<std::string> exchanges;
  exchanges = Split(s, ";");
  std::map<std::string, std::vector<std::string>> map;

  for (auto& section : exchanges) {
    // okex=A,B
    int idx = section.find('=');
    CHECK_NE_THROW(idx, std::string::npos) << "--symbols (-s) args is ill-formed";

    std::string mea = section.substr(0, idx);

    std::vector<std::string> symbols;
    symbols = Split(std::string_view(section.c_str() + idx + 1, section.size() - idx - 1), ",");

    map[mea] = symbols;
  }
  return map;
}

void AddDateOption(cxxopts::Options* opt, const char* flag) {
  opt->add_options()(flag, "date (e.g. 2019-12-27 or 20191227)", cxxopts::value<std::string>());
}

int64_t ParseDateOption(const cxxopts::ParseResult& res, const char* flag) {
  if (res.count(flag) == 0U) {
    THROW() << "no --" << flag << " is provided";
  }
  int64_t t = TimestampFromIso8601(res[flag].as<std::string>());
  CHECK_EQ_THROW(t % (24 * 60 * 60 * 1'000'000'000LL), 0) << "the time of the day is not 00:00";
  return t;
}

}  // namespace coin2::base::config
